﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Device.Location;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Animation;
using Microsoft.Phone.Controls;
using Tequila.AugmentedReality.Data;

namespace Tequila.AugmentedReality
{
    public partial class CompassControl : UserControl
    {
        private const int fullCircleAngle = 360;
        private const int halfOfViewSectorAngle = 32;
        private const int piOver2 = 90;

        private Dictionary<PointOfInterest, CompassPoi> _pois;
        private PageOrientation _currentPageOrientation;

        public static readonly DependencyProperty CurrentLocationProperty =
            DependencyProperty.Register("CurrentLocation",
                                        typeof (GeoCoordinate),
                                        typeof (CompassControl),
                                        new PropertyMetadata(null));

        public static readonly DependencyProperty PointsOfInterestProperty =
            DependencyProperty.Register("PointsOfInterest",
                                        typeof (IEnumerable<PointOfInterest>),
                                        typeof (CompassControl),
                                        new PropertyMetadata(null, PointsOfInterestPropertyChanged));

        public static readonly DependencyProperty AzimuthProperty =
            DependencyProperty.Register("Azimuth",
                                        typeof (double),
                                        typeof (CompassControl),
                                        new PropertyMetadata(0.0d, AzimuthDependencyPropertyChanged));

        public static readonly DependencyProperty RadiusProperty =
            DependencyProperty.Register("Radius",
                                        typeof (int),
                                        typeof (CompassControl),
                                        new PropertyMetadata(3000));

        public CompassControl()
        {
            InitializeComponent();
        }
       
        public int Radius
        {
            get { return (int) GetValue(RadiusProperty); }
            set { SetValue(RadiusProperty, value); }
        }

        public GeoCoordinate CurrentLocation
        {
            get { return (GeoCoordinate) GetValue(CurrentLocationProperty); }
            set { SetValue(CurrentLocationProperty, value); }
        }

        public double Azimuth
        {
            get { return (double) GetValue(AzimuthProperty); }
            set { SetValue(AzimuthProperty, value); }
        }

        public IEnumerable<PointOfInterest> PointsOfInterest
        {
            get { return GetValue(PointsOfInterestProperty) as IEnumerable<PointOfInterest>; }
            set { SetValue(PointsOfInterestProperty, value); }
        }

        private bool CanDrawPins
        {
            get { return CurrentLocation != null && !CurrentLocation.IsUnknown; }
        }

        private static void PointsOfInterestPropertyChanged(DependencyObject dependencyObject,
                                                    DependencyPropertyChangedEventArgs
                                                        dependencyPropertyChangedEventArgs)
        {
            var compassControl = (CompassControl) dependencyObject;

            compassControl.CreateInternalCollection();

            var observableCollection = compassControl.PointsOfInterest as INotifyCollectionChanged;
            if (observableCollection != null)
            {
                observableCollection.CollectionChanged += delegate { compassControl.CreateInternalCollection(); };
            }
        }

        private void CreateInternalCollection()
        {
            _pois = PointsOfInterest.ToDictionary(poi => poi, poi => new CompassPoi());                        

            PoiContainer.ItemsSource = _pois.Values;
        }

        private void OnAzimuthChanged()
        {
            //for different orientations this value is also different
            var deviceRotationAngle = _currentPageOrientation == PageOrientation.LandscapeLeft ? 90d : -90d;
            var northAngle = fullCircleAngle - Azimuth - deviceRotationAngle;

            BeginRotateAnimation(northAngle);

            if (CanDrawPins)
            {
                foreach (var poi in _pois)
                {
                    UpdatePoi(northAngle, poi.Key, poi.Value);
                }
            }
        }

        private void BeginRotateAnimation(double angle)
        {
            const int duration = 50;
            var storyboard = new Storyboard();

            var rotateAnimation = new DoubleAnimationUsingKeyFrames
                                      {
                                          Duration = TimeSpan.FromMilliseconds(duration)
                                      };

            rotateAnimation.KeyFrames.Add(new LinearDoubleKeyFrame
                                              {
                                                  Value = angle,
                                                  KeyTime = KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(duration))
                                              });


            Storyboard.SetTarget(rotateAnimation, GeoDirectionsRotation);
            Storyboard.SetTargetProperty(rotateAnimation, new PropertyPath(RotateTransform.AngleProperty));
            storyboard.Children.Add(rotateAnimation);

            storyboard.Begin();
        }

        private void UpdatePoi(double northAngle, PointOfInterest pointOfInterest, CompassPoi poi)
        {
            var course = ProjectionArithmetics.GetCourse(CurrentLocation, pointOfInterest.Location);

            //subtract 90 degress because difference between GeoDirectionsRotation angle and zero angle of circle is 90 
            var rotationAngle = course + northAngle - piOver2;
            var point = CreatePoint(rotationAngle, CalculateRadiusInPixel(pointOfInterest.Location));

            bool isVisible = pointOfInterest.Location.GetDistanceTo(CurrentLocation) < Radius;
            poi.IsVisible = isVisible;

            if (isVisible)
            {
                poi.X = point.X;
                poi.Y = point.Y;
                poi.IsInFov = IsInFov(course + northAngle);
            }
        }

        private Point CreatePoint(double rotationAngle, double radius)
        {
            var x = LayoutRoot.ActualWidth/2 + radius*Math.Cos(rotationAngle.ToRadians());
            var y = LayoutRoot.ActualHeight/2 + radius*Math.Sin(rotationAngle.ToRadians());
            return new Point(x, y);
        }

        private double CalculateRadiusInPixel(GeoCoordinate targetLocation)
        {
            //calculate how many meters in one pixel
            var pixInMeters = (VisibleSurface.Height/2)/Radius;
            var radiusInPix = CurrentLocation.GetDistanceTo(targetLocation)*pixInMeters;
            return radiusInPix;
        }

        private static bool IsInFov(double angle)
        {
            return Math.Abs(angle) <= halfOfViewSectorAngle ||
                   Math.Abs(fullCircleAngle - angle) <= halfOfViewSectorAngle;
        }

        private static void AzimuthDependencyPropertyChanged(DependencyObject sender,
                                                             DependencyPropertyChangedEventArgs args)
        {
            var compassControl = (CompassControl) sender;

            compassControl.OnAzimuthChanged();
        }

        public void ApplyOrientation(PageOrientation orientation)
        {
            _currentPageOrientation = orientation;
        }
    }
}